/*
 * ========THE SOLMIX PROJECT=====================================
 *
 * This is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License as
 * published by the Free Software Foundation; either version 2.1 of
 * the License, or (at your option) any later version.
 *
 * This software is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.gnu.org/licenses/ 
 * or see the FSF site: http://www.fsf.org. 
 */

package org.solmix.fmk.pool;

import java.util.HashMap;
import java.util.Map;

import org.solmix.commons.collections.DataTypeMap;
import org.solmix.commons.logs.Logger;
import org.solmix.commons.util.DataUtil;
import org.solmix.fmk.base.BaseConfig;
import org.solmix.fmk.datasource.PoolableDataSourceFactory;
import org.solmix.services.datasource.DSRequest;
import org.solmix.services.exception.SLXException;
import org.solmix.services.pool.IPoolableObjectFactory;
import org.solmix.services.pool.PoolService;
import org.solmix.services.pool.SlxKeyedPoolableObjectFactory;
import org.solmix.services.pool.SlxPoolableObjectFactory;
import org.solmix.services.types.Texception;
import org.solmix.services.types.Tmodule;

/**
 * 
 * @version $Id$
 */
@SuppressWarnings("unchecked")
public class PoolManager extends BaseConfig implements PoolService
{

    private final Logger log = new Logger(PoolManager.class.getName());

    protected IPoolableObjectFactory factory;

    protected String symbolicName;

    protected Object source;

    protected Map sources;

    protected String name;
    protected boolean enablePool;

    // private static final String FILTER = SLXConstants.SERVICE_CM_NAME + "=" + SLXConstants.MODULE_DS_NAME;
    //
    // @Inject
    // @Reference(id = "ds-config", serviceInterface = ConfigRealm.class, timeout = 100, filter = FILTER)

    public PoolManager(String name, IPoolableObjectFactory factory)
    {
        sources = new HashMap();
        this.name = name;
        this.factory = factory;
        symbolicName = getClass().getName();
    }

    public Object borrowObject(Object key) throws SLXException {
        return borrowObject(key, null);
    }

    /**
     * This method only used for datasource pool.
     * 
     * @param name
     * @param request {@link org.solmix.services.datasource.DSRequest}
     * @return
     * @throws SLXException
     */
    public Object borrowObject(Object key, DSRequest request) throws SLXException {
        Object objectSource = getObjectSource(key);
        if (objectSource instanceof SlxKeyedObjectPool)
            try {
                return ((SlxKeyedObjectPool) objectSource).borrowObject(key);
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Object faild", e);
            }
        if (objectSource instanceof SlxObjectPool)
            try {
                return ((SlxObjectPool) objectSource).borrowObject();
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Object faild", e);
            }
        if (objectSource instanceof SlxPoolableObjectFactory)
            try {
                return ((SlxPoolableObjectFactory) objectSource).makeObject();
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Object faild", e);
            }
        if (objectSource instanceof SlxKeyedPoolableObjectFactory)
            try {
                SlxKeyedPoolableObjectFactory source = (SlxKeyedPoolableObjectFactory) objectSource;
                if (source instanceof PoolableDataSourceFactory)
                    ((PoolableDataSourceFactory) source).setDsRequest(request);
                return source.makeObject(key);
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Object faild", e);
            }
        else
            throw new SLXException(Tmodule.POOL, Texception.POOL_INVALID_OBJECT_TYPE, "Invalid objectSource type:"
                + objectSource.getClass().getName(), new IllegalArgumentException());
    }

    public Object borrowUnpooledObject(Object key) throws SLXException {
        Object objectSource = getObjectSource(key);
        if (objectSource instanceof SlxKeyedObjectPool)
            try {
                return ((SlxKeyedObjectPool) objectSource).getObjectFactory().makeUnpooledObject(key);
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Unpool Object faild",
                    e);
            }
        if (objectSource instanceof SlxObjectPool)
            try {
                return ((SlxObjectPool) objectSource).getObjectFactory().makeUnpooledObject();
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Unpool Object faild",
                    e);
            }
        if (objectSource instanceof SlxPoolableObjectFactory)
            try {
                return ((SlxPoolableObjectFactory) objectSource).makeUnpooledObject();
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Unpool Object faild",
                    e);
            }
        if (objectSource instanceof SlxKeyedPoolableObjectFactory)
            try {
                return ((SlxKeyedPoolableObjectFactory) objectSource).makeUnpooledObject(key);
            } catch (Exception e) {
                throw new SLXException(Tmodule.POOL, Texception.POOL_BORROW_OBJECT_FAILD, "borrow Unpool Object faild",
                    e);
            }
        else
            throw new SLXException(Tmodule.POOL, Texception.POOL_INVALID_OBJECT_TYPE, "Invalid objectSource type:"
                + objectSource.getClass().getName(), new IllegalArgumentException());
    }

    public Object borrowNewObject(Object key) throws Exception {
        Object objectSource = getObjectSource(key);
        if (objectSource instanceof SlxKeyedObjectPool)
            return ((SlxKeyedObjectPool) objectSource).getObjectFactory().makeObject(key);
        if (objectSource instanceof SlxObjectPool)
            return ((SlxObjectPool) objectSource).getObjectFactory().makeObject();
        if (objectSource instanceof SlxPoolableObjectFactory)
            return ((SlxPoolableObjectFactory) objectSource).makeObject();
        if (objectSource instanceof SlxKeyedPoolableObjectFactory)
            return ((SlxKeyedPoolableObjectFactory) objectSource).makeObject(key);
        else
            throw new Exception((new StringBuilder()).append("Invalid objectSource type: ").append(
                objectSource.getClass().getName()).toString());
    }

    public void returnObject(Object key, Object obj) {
        try {
            Object objectSource = getObjectSource(key);
            if (objectSource instanceof SlxObjectPool)
                ((SlxObjectPool) objectSource).returnObject(obj);
            else if (objectSource instanceof SlxKeyedObjectPool)
                ((SlxKeyedObjectPool) objectSource).returnObject(key, obj);
            else if (objectSource instanceof SlxPoolableObjectFactory)
                ((SlxPoolableObjectFactory) objectSource).destroyObject(obj);
            else if (objectSource instanceof SlxKeyedPoolableObjectFactory)
                ((SlxKeyedPoolableObjectFactory) objectSource).destroyObject(key, obj);
        } catch (Exception e) {
            log.error("Error while freeing object - ignored.", e);
        }
    }

    public synchronized Object getObjectSource(Object key) throws SLXException {
        if (factory instanceof SlxKeyedPoolableObjectFactory) {
            if (this.source == null)
                this.source = makeSource(key);
            return this.source;
        }
        Object source = sources.get(key);
        if (source == null) {
            source = makeSource(key);
            sources.put(key, source);
        }
        return source;
    }

    protected Object makeSource( Object key ) throws SLXException
   {
      DataTypeMap golbalPool = globalConfig();
      DataTypeMap objectPool = getConfigRealm().getSubtree(name + ".pool");
      DataTypeMap thisPool = getConfigRealm().getSubtree(name + "." + key.toString() + ".pool");
      DataTypeMap mergConfig = new DataTypeMap();
      DataUtil.mapMerge(golbalPool, mergConfig);
      DataUtil.mapMerge(objectPool, mergConfig);
      DataUtil.mapMerge(thisPool, mergConfig);
      IPoolableObjectFactory factory = this.factory.newInstance( key );
      source = factory;
      SlxPoolableObjectFactory iscFactory = null;
      if ( factory instanceof SlxPoolableObjectFactory )
         iscFactory = (SlxPoolableObjectFactory) factory;
      if (mergConfig.getBoolean("enabled", false) && (iscFactory == null || !iscFactory.poolDisabled))
      {
          enablePool= DataUtil.asBoolean(mergConfig.remove("enabled"));
         Object pool;
         if ( factory instanceof SlxKeyedPoolableObjectFactory )
            pool = new SlxKeyedObjectPool((SlxKeyedPoolableObjectFactory) factory, mergConfig);
         else
            pool = new SlxObjectPool((SlxPoolableObjectFactory) factory, mergConfig);
         factory.setPool( pool );
         source = pool;
      } else
      {
         log.info( ( new StringBuilder() ).append( "Disabled for pooling '" ).append( key.toString() ).append( "' objects" ).toString() );
      }
      if ( source == null )
         throw new SLXException( Tmodule.POOL, Texception.POOL_UNABLE_BIND_OBJECT, "Unable to bind Object for key:" + key );
      else
         return source;
   }

    /**
     * {@inheritDoc}
     * 
     * @see org.solmix.fmk.base.BaseConfig#moduleName()
     */
    @Override
    public String moduleName() {
        return "pooling.default";
    }

    public void destroy() {
        if(source==null)
            return;
        try {
            if (this.source instanceof SlxKeyedObjectPool) {

                ((SlxKeyedObjectPool) source).close();

            } else {
                ((SlxObjectPool) source).close();
            }
        } catch (Exception e) {
            // ignored.
        }
    }
    public boolean isEnablePool(){
        return enablePool;
    }
    public Object getPool(){
        if(isEnablePool()){
            return source;
        }
        return null;
    }

}
